{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function\n",
    "import numpy as np\n",
    "\n",
    "from bqplot import (\n",
    "    Axis, ColorAxis, LinearScale, DateScale, DateColorScale, OrdinalScale,\n",
    "    OrdinalColorScale, ColorScale, Scatter, Lines, Figure, Tooltip\n",
    ")\n",
    "\n",
    "import ipywidgets as widgets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercise: Using one plot as a control for another\n",
    "\n",
    "Although [bqplot](https://github.com/bloomberg/bqplot) is a versatile plotting package, one of its unique strengths is that the plot is a widget. Plot elements, like the position of points on the graph, can be monitored for changes like any other widget trait. \n",
    "\n",
    "The example in this notebook graphs a Fourier sine series using [bqplot](https://github.com/bloomberg/bqplot) to graph the series and a separate [bqplot](https://github.com/bloomberg/bqplot) to allow users to sest the amplitude of the terms in the series.\n",
    "\n",
    "## Final widget will look like this:\n",
    "\n",
    "\n",
    "<img src=\"images/plot-as-control.gif\" width=\"50%\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define the function that calculates the Fourier series\n",
    "\n",
    "Though in a real application the plot range and number of points should perhaps be configurable via a widget, for this example they are hard coded."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fourier_series(amplitudes):\n",
    "    \"\"\"\n",
    "    Compute the fourier sine series given a set of amplitudes. The \n",
    "    period of the fundamental of the series is 1 and the series is \n",
    "    generated for two periods.\n",
    "    \"\"\"\n",
    "    period = 1.0\n",
    "    x = np.linspace(0, 2 * period, num=1000)\n",
    "    y = np.sum(a * np.sin(2 * np.pi * (n + 1) * x / period) \n",
    "               for n, a in enumerate(amplitudes))\n",
    "\n",
    "    return x, y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The number of Fourier components in the series should probably also be user-configurable; for the sake of simplicity it is hard coded here. We also define some test data so we can look at plot for setting the amplitudes before we connect it up to the plot of the Fourier series sum."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N_fourier_components = 10\n",
    "x_data = np.arange(N_fourier_components) + 1\n",
    "y_data = np.random.uniform(low=-1, high=1, size=N_fourier_components)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the amplitude control\n",
    "\n",
    "We will create both this and the plot of the series using the \"Grammar of Graphics\" interface to bqplot. That makes it easier to treat the plot as a widget.\n",
    "\n",
    "Please read through and execute the example below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Start by defining a scale for each axis\n",
    "sc_x = LinearScale()\n",
    "\n",
    "# The amplitudes are limited to ±1 for this example...\n",
    "sc_y = LinearScale(min=-1.0, max=1.0)\n",
    "\n",
    "# You can create a Scatter object without supplying the data at this\n",
    "# point. It is here so we can see how the control looks.\n",
    "scat = Scatter(x=x_data, y=y_data, \n",
    "               scales={'x': sc_x, 'y': sc_y}, \n",
    "               colors=['orange'],\n",
    "               # This is what makes this plot interactive\n",
    "               enable_move=True)\n",
    "\n",
    "# Only allow points to be moved vertically...\n",
    "scat.restrict_y = True\n",
    "\n",
    "# Define the axes themselves\n",
    "ax_x = Axis(scale=sc_x)\n",
    "ax_y = Axis(scale=sc_y, tick_format='0.2f', orientation='vertical')\n",
    "\n",
    "# The graph itself...\n",
    "amplitude_control = Figure(marks=[scat], axes=[ax_x, ax_y], \n",
    "                           title='Fourier amplitudes')\n",
    "\n",
    "# This width is chosen just to make the plot fit nicely with \n",
    "# another. Change it if you want.\n",
    "amplitude_control.layout.width = '400px'\n",
    "\n",
    "# Let's see what this looks like...\n",
    "amplitude_control"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try dragging the points on the graph around. Print the $y$-data from the plot (`scat.y`) and the original data below you they should be different."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set up some initial conditions\n",
    "\n",
    "To test our sine series plot it is helpful to start with a simple-to-understand series: just the fundmental, with amplitude 1. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add some test data to make view the result\n",
    "initial_amplitudes = np.zeros(10)\n",
    "initial_amplitudes[0] = 1.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the plot of the Fourier series\n",
    "\n",
    "As above, we create the plot using the Grammar of Graphics interface."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lin_x = LinearScale()\n",
    "lin_y = LinearScale()\n",
    "\n",
    "# Note that here, unlike above, we do not set the initial data.\n",
    "line = Lines(scales={'x': lin_x, 'y': lin_y}, colors=['orange'],\n",
    "               enable_move=False)\n",
    "\n",
    "ax_x = Axis(scale=lin_x)\n",
    "ax_y = Axis(scale=lin_y, tick_format='0.2f', orientation='vertical')\n",
    "\n",
    "result = Figure(marks=[line], axes=[ax_x, ax_y], \n",
    "                title='Fourier sine series',\n",
    "                # Honestly, I just like the way the animation looks.\n",
    "                # Value is in milliseconds.\n",
    "                animation_duration=500)\n",
    "\n",
    "# Size as you wish...\n",
    "result.layout.width = '400px'\n",
    "\n",
    "# Calculate the fourier series....\n",
    "line.x, line.y = fourier_series(initial_amplitudes)\n",
    "\n",
    "# Let's take a look!\n",
    "result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set the amplitude control to match this initial case\n",
    "\n",
    "Note that you can access the `scat` object from the `Figure` widget. Each line, scatter or other mark on the plot is in the list at `.marks`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "amplitude_control.marks[0].y = initial_amplitudes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## You make the widget out of `amplitude_control` and `result`\n",
    "\n",
    "See the animation above for a reminder of the target. Dragging the amplitudes will *not* change the sine series yet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/bqplot-as-control/box-widget.py\n",
    "box = widgets.HBox(children=[amplitude_control, result])\n",
    "box\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Looks good, connect things up\n",
    "\n",
    "Fill in the body of the function below, which will be observed by `amplitude_control`. You should call `fourier_series` and set the appropriate line properties."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/bqplot-as-control/update_line.py\n",
    "def update_line(change):\n",
    "    pass # fill in\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "# React to changes in the y value....\n",
    "amplitude_control.marks[0].observe(update_line, names=['y'])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
